﻿using System;
using System.IO;
using System.Text;
using System.Threading;

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace RVNetworkLib
{
    public class Client
    {
        // Member fields
        protected uint _userID;
        protected uint _roomID;

        protected Thread _receiverThread;

        protected bool _sendOK;
        protected AutoResetEvent _ackEvent;

        protected Packet _responsePacket;
        protected AutoResetEvent _responseEvent;

        // Accessors
        public ClientType Type { get; set; }

        public Client(ClientType type)
        {
            Type = type;
            _ackEvent = new AutoResetEvent(false);
            _responseEvent = new AutoResetEvent(false);
        }

        // Following three functions are override by concrete socket client
        public virtual ErrorCode Setup(string host, int port) 
        {
            // Create receiver thread
            _receiverThread = new Thread(() => ReceiveMsg());
            _receiverThread.Start();

            return ErrorCode.Success; 
        }

        public virtual void Teardown()
        {
            _receiverThread.Join();
            _receiverThread = null;

            _ackEvent.Close();
            _responseEvent.Close();

            _ackEvent = null;
            _responseEvent = null;
        }

        // Send function should wait ack to indicate success
        public ErrorCode Send(Packet p) 
        {
            _sendOK = false;

            ErrorCode err = SendPacket(p);
            if (err != ErrorCode.Success)
            {
                return err;
            }

            if (!_ackEvent.WaitOne(10000))
            {
                return ErrorCode.SendTimeout;
            }

            return _sendOK ? ErrorCode.Success : ErrorCode.NetworkError;
        }

        protected virtual ErrorCode SendPacket(Packet p)
        {
            return ErrorCode.NotImplemented;
        }

        protected ErrorCode GetResponse(ref Packet p)
        {
            if (!_responseEvent.WaitOne(10000))
            {
                return ErrorCode.ReceiveTimeout;
            }

            p = _responsePacket;
            return ErrorCode.Success;
        }

        // Recv function should send ack to indicate success
        protected virtual ErrorCode Recv(ref Packet p) { return ErrorCode.NotImplemented; }

        protected void ReceiveMsg()
        {
            try
            {
                while (true)
                {
                    Packet p = null;
                    ErrorCode err = Recv(ref p);

                    System.Diagnostics.Trace.WriteLine("[Client] receive message");

                    if (err != ErrorCode.Success)
                    {
                        break;
                    }

                    if (p.Type == PacketType.ACK)
                    {
                        System.Diagnostics.Trace.WriteLine("[Client] Receive ACK");
                        _sendOK = true;
                        _ackEvent.Set();
                    }
                    else if (p.Type == PacketType.NAK)
                    {
                        System.Diagnostics.Trace.WriteLine("[Client] Receive NAK");
                        _sendOK = false;
                        _ackEvent.Set();
                    }
                    
                    // Response type
                    else if ((((int)p.Type & 0x8000) != 0) ||
                        p.Type == PacketType.ACCEPT ||
                        p.Type == PacketType.REJECT)
                    {
                        System.Diagnostics.Trace.WriteLine(String.Format("[Client] Receive response {0}", p.Type));
                        _responsePacket = p;
                        _responseEvent.Set();
                    }
                    // Send type
                    else 
                    {
                        System.Diagnostics.Trace.WriteLine(String.Format("[Client] Receive send {0}", p.Type));

                        // Ack first
                        ReplyACK();

                        if (p.Type == PacketType.UPDATE_ROOM_STATUS)
                        {
                            //HandleUpdateRoomStatus(p);
                            string strPayload = Encoding.UTF8.GetString(p.Payload);
                            System.Diagnostics.Trace.WriteLine("Receive UpdateRoomStatus:");
                            System.Diagnostics.Trace.WriteLine(strPayload);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                System.Diagnostics.Trace.WriteLine("Receive error");
            }
        }

        public ErrorCode ReplyACK()
        {
            Packet p = new Packet()
                {
                    Type = PacketType.ACK,
                    UserID = _userID,
                };
            return SendPacket(p);
        }

        public ErrorCode ReplyNAK()
        {
            Packet p = new Packet()
                {
                    Type = PacketType.NAK,
                    UserID = _userID,
                };
            return SendPacket(p);
        }

        public ErrorCode ReplyAccept()
        {
            Packet p = new Packet()
                {
                    Type = PacketType.ACCEPT,
                    UserID = _userID,
                };
            return SendPacket(p);
        }

        public ErrorCode ReplyReject(RejectCause cause)
        {
            // Use LINQ to construct JSon Object
            var jsonString = new 
                {
                    ErrorCode = cause
                };

            byte[] payload = new UTF8Encoding().GetBytes(JsonConvert.SerializeObject(jsonString));

            Packet p = new Packet()
                {
                    Type = PacketType.REJECT,
                    UserID = _userID,
                    DataLength = (uint)payload.Length,
                    Payload = payload
                };
            return SendPacket(p);
        }

        public ErrorCode Register(string userName, string password)
        {
            // Use LINQ to construct JSon Object
            var jsonString = new
                {
                    UserName = userName,
                    Password = password
                };

            byte[] payload = new UTF8Encoding().GetBytes(JsonConvert.SerializeObject(jsonString));

            Packet p = new Packet()
                {
                    Type = PacketType.REGISTER,
                    UserID = 0,
                    DataLength = (uint)payload.Length,
                    Payload = payload
                };

            ErrorCode err = Send(p);
            if (err != ErrorCode.Success)
            {
                return err; 
            }

            err = GetResponse(ref p);
            if (err != ErrorCode.Success)
            {
                return err;
            }

            if (p.Type == PacketType.ACCEPT)
            {
                return ErrorCode.Success;
            }
            else if (p.Type == PacketType.REJECT)
            {
                // <FIXME> Hardcode to Duplicate error
                return ErrorCode.DuplicatedName;
            }
            else
            {
                return ErrorCode.NetworkError;
            }
        }

        public ErrorCode Login(string userName, string password)
        {
            // Use LINQ to construct JSon Object
            var jsonString = new
                {
                    UserName = userName,
                    Password = password
                };

            byte[] payload = new UTF8Encoding().GetBytes(JsonConvert.SerializeObject(jsonString));

            Packet p = new Packet()
                {
                    Type = PacketType.LOGIN,
                    UserID = 0,
                    DataLength = (uint)payload.Length,
                    Payload = payload
                };

            ErrorCode err = Send(p);
            if (err != ErrorCode.Success)
            {
                return err;
            }

            err = GetResponse(ref p);
            if (err != ErrorCode.Success)
            {
                return err;
            }

            if (p.Type == PacketType.REJECT)
            {
                JObject o = JObject.Parse(new UTF8Encoding().GetString(p.Payload));

                switch ((uint)o["ErrorCode"])
                {
                    case 0x2: return ErrorCode.UserNotFound;
                    case 0x3: return ErrorCode.IncorrectPassword;
                    default: return ErrorCode.NetworkError;
                }
            }
            else
            {
                _userID = p.UserID;
                return ErrorCode.Success;
            }
        }

        public ErrorCode Logout(uint userID)
        {
            Packet p = new Packet()
                {
                    Type = PacketType.LOGOUT,
                    UserID = _userID
                };

            ErrorCode err = Send(p);
            if (err != ErrorCode.Success)
            {
                return err;
            }

            err = GetResponse(ref p);
            if (err != ErrorCode.Success)
            {
                return err;
            }

            if (p.Type == PacketType.ACCEPT)
            {
                return ErrorCode.Success;
            }
            else
            {
                return ErrorCode.NetworkError;
            }
        }

        private ErrorCode Ping()
        {
            return ErrorCode.NotImplemented;
        }

        public ErrorCode QueryRoomList(
            uint roomIDStart, 
            uint queryCount,
            ref BriefRoomStatus brs)
        {
            // Use LINQ to construct JSon Object
            var jsonString = new
                {
                    RoomIDStart = roomIDStart,
                    QueryCount = queryCount
                };

            byte[] payload = new UTF8Encoding().GetBytes(JsonConvert.SerializeObject(jsonString));

            Packet p = new Packet()
                {
                    Type = PacketType.QUERY_ROOM_LIST,
                    UserID = _userID,
                    DataLength = (uint)payload.Length,
                    Payload = payload
                };

            ErrorCode err = Send(p);
            if (err != ErrorCode.Success)
            {
                return err;
            }

            err = GetResponse(ref p);
            if (err != ErrorCode.Success)
            {
                return err;
            }

            if (p.Type == PacketType.REJECT)
            {
                // It should impossible to reject.
                return ErrorCode.NetworkError;
            }
            else
            {
                brs = JsonConvert.DeserializeObject<BriefRoomStatus>(new UTF8Encoding().GetString(p.Payload));
                return ErrorCode.Success;
            }
        }

        public ErrorCode CreateRoom(
            string roomName,
            string mapName,
            uint maxUser,
            GameMode mode,
            ref RoomStatus rs)
        {
            // Use LINQ to construct JSon Object
            var jsonString = new
                {
                    RoomName = roomName,
                    MapName = mapName,
                    MaxUser = maxUser,
                    Mode = (int)mode
                };

            byte[] payload = new UTF8Encoding().GetBytes(JsonConvert.SerializeObject(jsonString));

            Packet p = new Packet()
                {
                    Type = PacketType.CREATE_ROOM,
                    UserID = _userID,
                    DataLength = (uint)payload.Length,
                    Payload = payload
                };

            ErrorCode err = Send(p);
            if (err != ErrorCode.Success)
            {
                return err;
            }

            err = Recv(ref p);
            if (err != ErrorCode.Success)
            {
                return err;
            }

            if (p.Type == PacketType.REJECT)
            {
                // It should impossible to reject.
                return ErrorCode.NetworkError;
            }
            else
            {
                rs = JsonConvert.DeserializeObject<RoomStatus>(new UTF8Encoding().GetString(p.Payload));
                _roomID = rs.RoomID;
                return ErrorCode.Success;
            }
        }

        public ErrorCode JoinRoom(uint roomID, ref RoomStatus rs)
        {
            // Use LINQ to construct JSon Object
            var jsonString = new
                {
                    RoomID = roomID
                };

            byte[] payload = new UTF8Encoding().GetBytes(JsonConvert.SerializeObject(jsonString));

            Packet p = new Packet()
                {
                    Type = PacketType.JOIN_ROOM,
                    UserID = _userID,
                    DataLength = (uint)payload.Length,
                    Payload = payload
                };

            ErrorCode err = Send(p);
            if (err != ErrorCode.Success)
            {
                return err;
            }

            err = Recv(ref p);
            if (err != ErrorCode.Success)
            {
                return err;
            }

            if (p.Type == PacketType.REJECT)
            {
                // It should impossible to reject.
                return ErrorCode.NetworkError;
            }
            else
            {
                rs = JsonConvert.DeserializeObject<RoomStatus>(new UTF8Encoding().GetString(p.Payload));
                _roomID = rs.RoomID;
                return ErrorCode.Success;
            }
        }

        public ErrorCode LeaveRoom()
        {
            Packet p = new Packet()
            {
                Type = PacketType.LEAVE_ROOM,
                UserID = _userID
            };

            ErrorCode err = Send(p);
            if (err != ErrorCode.Success)
            {
                return err;
            }

            err = Recv(ref p);
            if (err != ErrorCode.Success)
            {
                return err;
            }

            if (p.Type == PacketType.ACCEPT)
            {
                return ErrorCode.Success;
            }
            else
            {
                return ErrorCode.NetworkError;
            }
        }

        public ErrorCode UpdateState(
            ClientStatus status,
            float mileage,
            float gpsX,
            float gpsY)
        {
            // Use LINQ to construct JSon Object
            var jsonString = new
                {
                    State = status,
                    Mileage = mileage,
                    GPS_X = gpsX,
                    GPS_Y = gpsY
                };

            byte[] payload = new UTF8Encoding().GetBytes(JsonConvert.SerializeObject(jsonString));

            Packet p = new Packet()
                {
                    Type = PacketType.UPDATE_STATE,
                    UserID = _userID,
                    DataLength = (uint)payload.Length,
                    Payload = payload
                };

            ErrorCode err = Send(p);
            if (err != ErrorCode.Success)
            {
                return err;
            }

            err = Recv(ref p);
            if (err != ErrorCode.Success)
            {
                return err;
            }

            if (p.Type == PacketType.REJECT)
            {
                // It should impossible to reject.
                return ErrorCode.NetworkError;
            }
            else
            {
                return ErrorCode.Success;
            }
        }

        public ErrorCode StartGame()
        {
            Packet p = new Packet()
            {
                Type = PacketType.START_GAME,
                UserID = _userID,
            };

            ErrorCode err = Send(p);
            if (err != ErrorCode.Success)
            {
                return err;
            }

            err = Recv(ref p);
            if (err != ErrorCode.Success)
            {
                return err;
            }

            if (p.Type == PacketType.REJECT)
            {
                // It should impossible to reject.
                return ErrorCode.NetworkError;
            }
            else
            {
                return ErrorCode.Success;
            }
        }

        public ErrorCode Chat(string text)
        {
            // Use LINQ to construct JSon Object
            var jsonString = new
            {
                RoomID = _roomID,
                Content = text
            };

            byte[] payload = new UTF8Encoding().GetBytes(JsonConvert.SerializeObject(jsonString));

            Packet p = new Packet()
            {
                Type = PacketType.CHAT,
                UserID = _userID,
                DataLength = (uint)payload.Length,
                Payload = payload
            };

            ErrorCode err = Send(p);
            if (err != ErrorCode.Success)
            {
                return err;
            }

            err = Recv(ref p);
            if (err != ErrorCode.Success)
            {
                return err;
            }

            if (p.Type == PacketType.REJECT)
            {
                // It should impossible to reject.
                return ErrorCode.NetworkError;
            }
            else
            {
                return ErrorCode.Success;
            }
        }

        public ErrorCode SendAudio(byte[] data)
        {
            byte[] payload = null;
            using (MemoryStream ms = new MemoryStream())
            {
                using (BinaryWriter writer = new BinaryWriter(ms))
                {
                    writer.Write(_roomID);
                    writer.Write(data);
                }
                payload = ms.ToArray();
            }

            Packet p = new Packet()
            {
                Type = PacketType.AUDIO,
                UserID = _userID,
                DataLength = (uint)payload.Length,
                Payload = payload
            };

            ErrorCode err = Send(p);
            if (err != ErrorCode.Success)
            {
                return err;
            }

            return ErrorCode.Success;
        }

        public ErrorCode SendVideo(byte[] data)
        {
            byte[] payload = null;
            using (MemoryStream ms = new MemoryStream())
            {
                using (BinaryWriter writer = new BinaryWriter(ms))
                {
                    writer.Write(_roomID);
                    writer.Write(data);
                }
                payload = ms.ToArray();
            }

            Packet p = new Packet()
            {
                Type = PacketType.VIDEO,
                UserID = _userID,
                DataLength = (uint)payload.Length,
                Payload = payload
            };

            ErrorCode err = Send(p);
            if (err != ErrorCode.Success)
            {
                return err;
            }

            return ErrorCode.Success;
        }
    }
}
